import re

from django.db import models

from settings import IMAGES_FOLDER, MEDIA_ROOT

class Lang(models.Model):
    """ The list of languages installed in Djips.
    """
    id = models.CharField(max_length=5, primary_key=True)
    name = models.CharField(max_length=50)
    activated = models.BooleanField(default=False)
    default = models.BooleanField(default=False)

    filter_from_deletion_warning = True

    def __unicode__(self):
	return self.name


class Color(models.Model):
    """ Each Image can be tagged with one color, allowing
    an alternative to tags.
    It is possible to set a name to each color.
    """
    value = models.CharField(max_length=6) # Hexadecimal name

    def __unicode__(self):
	return self.value + ' ' + self.name

    def _get_default_attr(self):
	""" Returns the default attributes.
	"""
        for attr in self.attributes.all():
            if attr.lang.default:
                return attr

	# No attributes found, we create one.
	default_language = Lang.objects.get(default=True)

	attr = ColorAttr(color = self,
			 lang = default_language)
	attr.save()
	return attr
	
    def _get_name(self):
	""" Returns the name in the default language.
	"""
	return self._get_default_attr().name

    def _set_name(self, value):
	""" Sets the name in the default language.
	"""
	attr = self._get_default_attr()
	attr.name = value
	attr.save()

    # The color name in the default language.
    name = property(_get_name, _set_name)

class ColorAttr(models.Model):
    """ Internationalization for the colors.
    """
    name = models.CharField(max_length=100,
                     blank=True)
    lang = models.ForeignKey(Lang)
    color = models.ForeignKey(Color,
			      related_name='attributes')

    filter_from_deletion_warning = True
    
    def __unicode__(self):
	return self.name + " " + self.color.value + " " + self.lang.name


class Tag(models.Model):
    """ A tag is a word that can be applied to an image.
    There is a notion of hierarchy in tags, due to the fact
    that a tag can be nested in another one
    For example the tag 'bird' can be nested in the 'animal' tag,
    so each Image tagged with 'bird' will automatically be tagged
    with 'animal'.
    """
    father_tag = models.ForeignKey("self", blank=True, null=True,
                                   related_name='son_tags')

    def __unicode__(self):
	return str(self.id) + ' - ' + self.name

    def ancestors(self):
	""" Provides the list of Tag's ancestors.
	"""
	ancestors = []
	father = self.father_tag
	
	while father:
	    ancestors.insert(0, father)
	    father = father.father_tag

	return ancestors

    def _get_default_attr(self):
	""" Returns the default attributes.
	"""
        for attr in self.attributes.all():
            if attr.lang.default:
                return attr

	# No attributes found, we create one.
	default_language = Lang.objects.get(default=True)

	attr = TagAttr(tag = self,
		       lang = default_language)
	attr.save()
	return attr
	
    def _get_name(self):
	""" Returns the name in the default language.
	"""
	return self._get_default_attr().name

    def _set_name(self, value):
	""" Sets the name in the default language.
	"""
	attr = self._get_default_attr()
	attr.name = value
	attr.save()

    # The tag name in the default language.
    name = property(_get_name, _set_name)

    @property
    def is_leaf(self):
	""" True is the tag has no sons.
	"""
	return not self.son_tags.all()

    @property
    def is_root(self):
	""" True is the tag has no father.
	"""
	return not self.father_tag


class TagAttr(models.Model):
    """ Internationalisation for the tags.
    """
    name = models.CharField(max_length=100)
    lang = models.ForeignKey(Lang)
    tag = models.ForeignKey(Tag,
			      related_name='attributes')

    filter_from_deletion_warning = True

    def __unicode__(self):
	return self.name


class Folder(models.Model):
    """ Represents the folder in which the Image is stored.
    It basically provides the same structure than a classical
    file system.
    """
    dir = models.CharField(max_length=2000)
    name = models.CharField(max_length=100)
    sup_folder = models.ForeignKey("self", blank=True, null=True,
                                   related_name='sub_folders')

    def __unicode__(self):
	return '%s (%s)' % (self.name, self.dir)

    def list_path(self):
	path = []
	sup_folder = self.sup_folder
	
	while sup_folder:
	    path.insert(0, sup_folder)
	    sup_folder = sup_folder.sup_folder

	return path

    def all_folders(self):
	""" Finds all sub-folders of the current one 
	(including sub-sub-sub-...-folders)
	"""
	folders = []
	path = [self]
	while path:
	    current_folder = path.pop()
	    path.extend(current_folder.sub_folders.all())
	    if not self == current_folder:
		folders.append(current_folder)

	return folders

    def all_images(self):
        """ Finds all images contained in
        the folder and the sub-folders.
        """
	images = []
	images.extend(self.images.all())

	for folder in self.all_folders():
	    images.extend(folder.images.all())

	return images


class Image(models.Model, object):
    """ An image published on the site.
    """
    file = models.ImageField(upload_to=IMAGES_FOLDER,
			     max_length=300)
    thumbnail = models.ImageField(upload_to=IMAGES_FOLDER,
				  max_length=300)

    mark = models.SmallIntegerField()
    color_flag = models.ForeignKey(Color, blank=True, null=True,
                                   related_name='images')
    tags = models.ManyToManyField(Tag,
                                  related_name='images')
    folder = models.ForeignKey(Folder,
                               related_name='images')

    def __unicode__(self):
	return self.file.path

    def folder_chain(self):
        """ Returns the list of folder the image belongs to.
        For example, the image /var/www/site1/img will return
        ['var', 'www', 'site1', 'img']
        """
	return self.folder.list_path()

    def _default_attr(self):
	""" Gets the Attribute of this image for the default
	language.
	"""
        for attr in self.attributes.all():
            if attr.lang.default:
                return attr

	# No attributes found, we create one.
	default_language = Lang.objects.get(default=True)

	attr = ImageAttr(image = self,
			 lang = default_language)
	attr.save()
	return attr

    def _get_title(self):
        """ Gets the title in the default language
	"""
	return self._default_attr().title

    def _set_title(self, value):
	"""  Sets the title in the default language.
	"""
        attr  = self._default_attr()
	attr.title = value
	attr.save()

    # The image title in the default language.    
    title = property(_get_title, _set_title)

    def _get_description(self):
        """ Gets the description in the default language
	"""
	return self._default_attr().description

    def _set_description(self, value):
	"""  Sets the description in the default language.
	"""
        attr  = self._default_attr()
	attr.description = value
	attr.save()

    # The image description in the default language.
    description = property(_get_description, _set_description)

    def update_tags(self, tags_str):
	""" Takes a strings describing the tags and update the tag
	attributes.
	The 'tags_str' string must have this format:
	' tags1 > tag2 > tag 3, tag2 > tag4 '

	In this example we have a tag 'tag1' which contains tag 'tag2',
	this last one containing two tags, 'tag3' and 'tag4'.
	"""
	
	def clean_tag(tag):
	    """ Removes white characters (spaces, tab, carriage ...)
	    before and after the tag.

	    Already clean tag.
	    >>> clean_tag('grep')
	    grep

	    Tag with crap at start.
	    >>> clean_tag('   \n  blabla')
	    blabla

	    Tag with crap at the end.
	    >>> clean_tag('fich  \t  ')
	    fich

	    Tag with multiple words in it and some bad separations.
	    >>> clean_tag('  grep  \tblbla\n  \t fich  \n')
	    grep blabla fich
	    """
	    return ' '.join([word for word in re.split('\s+', tag) if word])

	# List of tags described in the string.
	tags = []
	default_language = Lang.objects.get(default=True)

	# List of tags for whom the father_tag has been changed.
	father_changed = []

	for serie in tags_str.split(','):
	    serie_tags = serie.split('>')

	    father_tag = None

	    for tag in serie_tags:
		tag = clean_tag(tag)
	    
		# We find the tag in the database. We can not directly
		# get it by its name, we need to use TagAttr.
		attrs = TagAttr.objects.filter(name = tag,
					       lang = default_language)

		if attrs:
		    # We found an attribute, maybe more than one but
		    # this shall not happen.
		    db_tag = attrs[0].tag
		    
		    if father_tag:
			if father_tag.name != db_tag.father_tag:
			    # In this case, a new father has been specified.
			    db_tag.father_tag = father_tag
			    db_tag.save()
			    father_changed.append(db_tag)
		else:
		    # We create it.
		    db_tag = Tag(father_tag = father_tag)
		    db_tag.save()
		    db_tag.name = tag

		# We store the tag.
		tags.append(db_tag)

		# We keep it as father tag for the
		# next tag coming.
		father_tag = db_tag

	# Now we have all tags in the 'tags' list.
	# We compute the full tree of tags (for each tag, we find his
	# father, grand father ...)
	# This is done to simplify request when trying to fetch all images
	# tagged with a simple tag, instead of computing the tree at each
	# request.
	tree = []

	for tag in tags:
	    if tag in tree:
		# If the tag is already in three, its parents are also in,
		# so no need to compute family.
		continue

	    family = [tag]
	    current_tag = tag.father_tag

	    # We compute tag's family (until reaching the root tag
	    # or a tag already stored in the tree)
	    while current_tag:
		if current_tag in tree:
		    break

		family.append(current_tag)
		current_tag = current_tag.father_tag

	    tree.extend(family)

	# Now we can replace all tags in the image.
	self.tags.clear()
	self.tags.add(*tree)

	# The last step is to update all images tags when father_tags
	# has been changed.
	for tag in father_changed:
	    for img in tag.images.all():
		img.update_tags(img.tags_as_list())

    def tags_as_list(self):
	""" Returns a list looking like this 'tags1, tag3, tag4, ..., tag10'
	Only leaf tags are displayed.
	"""
	return ', '.join([tag.name for tag in self.tags.all() if tag.is_leaf])

class ImageAttr(models.Model):
    """ Internationalization for the Images.
    """
    title = models.CharField(max_length=100)
    description = models.TextField(blank=True)
    lang = models.ForeignKey(Lang)
    image = models.ForeignKey(Image,
			      related_name='attributes')

    filter_from_deletion_warning = True

    def __unicode__(self):
	return self.title + ' (' + self.lang.id + ')'
